package com.czarea.pay.sdk.wxpay.transport;

import com.czarea.pay.sdk.parallel.DefaultThreadFactory;
import com.czarea.pay.sdk.wxpay.config.WxPayConfig;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.BasicHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

/**
 * 交易保障
 *
 * @author zhouzx
 */
public class WxPayReport {

    public static class ReportInfo {

        /**
         * 布尔变量使用int。0为false， 1为true。
         */

        // 基本信息
        private String version = "v0";
        private String sdk = "wxpay java sdk v1.0";
        private String uuid; // 交易的标识
        private long timestamp; // 上报时的时间戳，单位秒
        private long elapsedTimeMillis; // 耗时，单位 毫秒

        // 针对主域名
        private String firstDomain; // 第1次请求的域名
        private boolean primaryDomain; // 是否主域名
        private int firstConnectTimeoutMillis; // 第1次请求设置的连接超时时间，单位 毫秒
        private int firstReadTimeoutMillis; // 第1次请求设置的读写超时时间，单位 毫秒
        private int firstHasDnsError; // 第1次请求是否出现dns问题
        private int firstHasConnectTimeout; // 第1次请求是否出现连接超时
        private int firstHasReadTimeout; // 第1次请求是否出现连接超时

        public ReportInfo(String uuid, long timestamp, long elapsedTimeMillis, String firstDomain,
            boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
            boolean firstHasDnsError, boolean firstHasConnectTimeout,
            boolean firstHasReadTimeout) {
            this.uuid = uuid;
            this.timestamp = timestamp;
            this.elapsedTimeMillis = elapsedTimeMillis;
            this.firstDomain = firstDomain;
            this.primaryDomain = primaryDomain;
            this.firstConnectTimeoutMillis = firstConnectTimeoutMillis;
            this.firstReadTimeoutMillis = firstReadTimeoutMillis;
            this.firstHasDnsError = firstHasDnsError ? 1 : 0;
            this.firstHasConnectTimeout = firstHasConnectTimeout ? 1 : 0;
            this.firstHasReadTimeout = firstHasReadTimeout ? 1 : 0;
        }

        @Override
        public String toString() {
            return "ReportInfo{" + "version='" + version + '\'' + ", sdk='" + sdk + '\''
                + ", uuid='" + uuid + '\'' + ", timestamp=" + timestamp + ", elapsedTimeMillis="
                + elapsedTimeMillis + ", firstDomain='" + firstDomain + '\''
                + ", primaryDomain=" + primaryDomain + ", firstConnectTimeoutMillis="
                + firstConnectTimeoutMillis + ", firstReadTimeoutMillis="
                + firstReadTimeoutMillis + ", firstHasDnsError=" + firstHasDnsError
                + ", firstHasConnectTimeout=" + firstHasConnectTimeout
                + ", firstHasReadTimeout=" + firstHasReadTimeout + '}';
        }

        /**
         * 转换成 csv 格式
         */
        public String toLineString(String key) {
            String separator = ",";
            Object[] objects = new Object[]{version, sdk, uuid, timestamp, elapsedTimeMillis,
                firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
                firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout};
            StringBuffer sb = new StringBuffer();
            for (Object obj : objects) {
                sb.append(obj).append(separator);
            }
            try {
                String sign = WxPayUtil.HMACSHA256(sb.toString(), key);
                sb.append(sign);
                return sb.toString();
            } catch (Exception ex) {
                return null;
            }

        }

    }

    private static final String REPORT_URL = "http://report.mch.weixin.qq.com/wxpay/report/default";
    // private static final String REPORT_URL = "http://127.0.0.1:5000/test";


    private static final int DEFAULT_CONNECT_TIMEOUT_MS = 6 * 1000;
    private static final int DEFAULT_READ_TIMEOUT_MS = 8 * 1000;

    private LinkedBlockingQueue<String> reportMsgQueue = null;
    private WxPayConfig config;
    private ExecutorService executorService;

    private static final int CORE_POOL_SIZE = 2;
    private static final int MAXIMUM_POOL_SIZE = 10;

    private volatile static WxPayReport INSTANCE;

    private WxPayReport(final WxPayConfig config) {
        this.config = config;
        reportMsgQueue = new LinkedBlockingQueue<String>(config.getReportQueueMaxSize());

        ThreadFactory namedThreadFactory = new DefaultThreadFactory();
        executorService = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());

        if (config.isShouldAutoReport()) {
            WxPayUtil.getLogger().info("report worker num: {}", config.getReportWorkerNum());
            for (int i = 0; i < config.getReportWorkerNum(); ++i) {
                executorService.execute(new Runnable() {
                    @Override
                    public void run() {
                        while (true) {
                            // 先用 take 获取数据
                            try {
                                StringBuffer sb = new StringBuffer();
                                String firstMsg = reportMsgQueue.take();
                                WxPayUtil.getLogger().info("get first report msg: {}", firstMsg);
                                String msg = null;
                                sb.append(firstMsg); // 会阻塞至有消息
                                int remainNum = config.getReportBatchSize() - 1;
                                for (int j = 0; j < remainNum; ++j) {
                                    WxPayUtil.getLogger().info("try get remain report msg");
                                    // msg = reportMsgQueue.poll(); // 不阻塞了
                                    msg = reportMsgQueue.take();
                                    WxPayUtil.getLogger().info("get remain report msg: {}", msg);
                                    if (msg == null) {
                                        break;
                                    } else {
                                        sb.append("\n");
                                        sb.append(msg);
                                    }
                                }
                                // 上报
                                WxPayReport.httpRequest(sb.toString(), DEFAULT_CONNECT_TIMEOUT_MS,
                                    DEFAULT_READ_TIMEOUT_MS);
                            } catch (Exception ex) {
                                WxPayUtil.getLogger().warn("report fail. reason: {}",
                                    ex.getMessage());
                            }
                        }
                    }
                });
            }
        }

    }

    /**
     * 单例，双重校验，请在 JDK 1.5及更高版本中使用
     */
    public static WxPayReport getInstance(WxPayConfig config) {
        if (INSTANCE == null) {
            synchronized (WxPayReport.class) {
                if (INSTANCE == null) {
                    INSTANCE = new WxPayReport(config);
                }
            }
        }
        return INSTANCE;
    }

    public void report(String uuid, long elapsedTimeMillis, String firstDomain,
        boolean primaryDomain, int firstConnectTimeoutMillis, int firstReadTimeoutMillis,
        boolean firstHasDnsError, boolean firstHasConnectTimeout, boolean firstHasReadTimeout,String source) {
        long currentTimestamp = WxPayUtil.getCurrentTimestamp();
        ReportInfo reportInfo = new ReportInfo(uuid, currentTimestamp, elapsedTimeMillis,
            firstDomain, primaryDomain, firstConnectTimeoutMillis, firstReadTimeoutMillis,
            firstHasDnsError, firstHasConnectTimeout, firstHasReadTimeout);
        String data = reportInfo.toLineString(config.getKey().getOrDefault(source,null));
        WxPayUtil.getLogger().info("report {}", data);
        if (data != null) {
            reportMsgQueue.offer(data);
        }
    }

    /**
     * http 请求
     */
    private static String httpRequest(String data, int connectTimeoutMs, int readTimeoutMs)
        throws Exception {
        BasicHttpClientConnectionManager connManager;
        connManager = new BasicHttpClientConnectionManager(
            RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", SSLConnectionSocketFactory.getSocketFactory()).build(),
            null, null, null);
        HttpClient httpClient =
            HttpClientBuilder.create().setConnectionManager(connManager).build();

        HttpPost httpPost = new HttpPost(REPORT_URL);

        RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(readTimeoutMs)
            .setConnectTimeout(connectTimeoutMs).build();
        httpPost.setConfig(requestConfig);

        StringEntity postEntity = new StringEntity(data, "UTF-8");
        httpPost.addHeader("Content-Type", "text/xml");
        httpPost.addHeader("User-Agent", "wxpay sdk java v1.0 "); // TODO: 很重要，用来检测 sdk
        // 的使用情况，要不要加上商户信息？
        httpPost.setEntity(postEntity);

        HttpResponse httpResponse = httpClient.execute(httpPost);
        HttpEntity httpEntity = httpResponse.getEntity();
        return EntityUtils.toString(httpEntity, "UTF-8");
    }

}
